﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Windows.Input;
using MultiLanguage.Infrastructure;

namespace MultiLanguage
{
    /// <summary>
    /// Provides a culture-agnostic context for a temperature conversion application
    /// which is based upon the MVVM pattern.  The View Model does not care what language
    /// appears on the user surface and can change cultures dynamically.
    /// 
    /// 
    /// Injections......
    /// ----------- A model that performs the requisite calculations
    /// ----------- A list of cultures to be supported
    /// 
    /// If no cultures are provided, the View Model will fall back to the 
    /// invariant culture
    /// 
    /// If a model is not provided, the View Model will not crash
    /// but the application will have limited functionality
    /// 
    /// </summary>
    public sealed class TemperatureConverterViewModel : IViewModel
    {
        private SupportedCultureInfo _selectedCultureInfo;
        private bool _hasError;
        private bool _isDirectionalToggleChecked;
        private readonly ITemperatureConverter _temperatureConverter;
        private static readonly List<PropertyInfo> NotifyingStrings;
        private static readonly Type VmType;
        private static readonly string Invalidnumber;
        private string _celsiusToFahrenheit;
        private string _degreesText;
        private string _directionalToolTip;
        private string _fahrenheitToCelsius;
        private string _inputTemperature;
        private string _inputTemperatureToolTip;
        private string _menuEdit;
        private string _menuFile;
        private string _menuHelp;
        private string _menuView;
        private string _outputTemperature;
        private string _selfCaller;
        private string _statusText;
        private double _inputParseResult = 12.34;
        public ObservableCollection<ISupportedCultureInfo> SupportedCultures { get; set; }
        public ICommand SetCultureCommand { get; set; }
        static TemperatureConverterViewModel()
        {
            Invalidnumber = "InvalidNumber";
            NotifyingStrings = new List<PropertyInfo>();
            VmType = typeof (TemperatureConverterViewModel);
            foreach (PropertyInfo pi in VmType.GetProperties().Where(q => q.PropertyType == typeof (string) && q.CanRead && q.CanWrite))
            {
                if (pi.GetGetMethod(false).IsPublic)
                {
                    NotifyingStrings.Add(pi);
                }
            }
        }
        public TemperatureConverterViewModel(ITemperatureConverter converter, IEnumerable<ISupportedCultureInfo> cultures)
        {
            _temperatureConverter = converter;
            SetCultureCommand = new RelayCommand(ExecuteSetCultureCommand, x => true);
            if (cultures != null && cultures.Any())
            {
                SupportedCultures = new ObservableCollection<ISupportedCultureInfo>(cultures);
            }
            else
            {
                SupportedCultures = new ObservableCollection<ISupportedCultureInfo>
                {
                    new SupportedCultureInfo(CultureInfo.InvariantCulture, null)
                };
            }
            InputTemperature = _inputParseResult.ToString("N", CultureInfo.CurrentCulture);
            SelectedCultureInfo = SupportedCultures.Last() as SupportedCultureInfo;
            ExecuteSetCultureCommand(null);
            IsFocused = true;
        }
        #region Notifying Properties
        public string InputTemperature
        {
            [DebuggerStepThrough] get { return _inputTemperature; }
            [DebuggerStepThrough]
            set
            {
                if (value != _inputTemperature)
                {
                    _inputTemperature = value;
                    OnPropertyChanged("InputTemperature");
                }
            }
        }
        public string StatusText
        {
            [DebuggerStepThrough] get { return _statusText; }
            [DebuggerStepThrough]
            set
            {
                if (value != _statusText)
                {
                    _statusText = value;
                    OnPropertyChanged("StatusText");
                }
            }
        }
        public bool IsDirectionalToggleChecked
        {
            [DebuggerStepThrough] get { return _isDirectionalToggleChecked; }
            [DebuggerStepThrough]
            set
            {
                if (value != _isDirectionalToggleChecked)
                {
                    _isDirectionalToggleChecked = value;
                    OnPropertyChanged("IsDirectionalToggleChecked");
                    _selfCaller = this["InputTemperature"];
                }
            }
        }
        public string FahrenheitToCelsius
        {
            [DebuggerStepThrough] get { return _fahrenheitToCelsius; }
            [DebuggerStepThrough]
            set
            {
                if (value != _fahrenheitToCelsius)
                {
                    _fahrenheitToCelsius = value;
                    OnPropertyChanged("FahrenheitToCelsius");
                }
            }
        }
        public string DegreesText
        {
            [DebuggerStepThrough] get { return _degreesText; }
            [DebuggerStepThrough]
            set
            {
                if (value != _degreesText)
                {
                    _degreesText = value;
                    OnPropertyChanged("DegreesText");
                }
            }
        }
        public string OutputTemperature
        {
            [DebuggerStepThrough] get { return _outputTemperature; }
            [DebuggerStepThrough]
            set
            {
                if (value != _outputTemperature)
                {
                    _outputTemperature = value;
                    OnPropertyChanged("OutputTemperature");
                }
            }
        }
        public string CelsiusToFahrenheit
        {
            [DebuggerStepThrough] get { return _celsiusToFahrenheit; }
            [DebuggerStepThrough]
            set
            {
                if (value != _celsiusToFahrenheit)
                {
                    _celsiusToFahrenheit = value;
                    OnPropertyChanged("CelsiusToFahrenheit");
                }
            }
        }
        public SupportedCultureInfo SelectedCultureInfo
        {
            [DebuggerStepThrough] get { return _selectedCultureInfo; }
            [DebuggerStepThrough]
            set
            {
                if (value != _selectedCultureInfo)
                {
                    _selectedCultureInfo = value;
                    OnPropertyChanged("SelectedCultureInfo");
                    ExecuteSetCultureCommand(_selectedCultureInfo);
                }
            }
        }
        public string InputTemperatureToolTip
        {
            [DebuggerStepThrough] get { return _inputTemperatureToolTip; }
            [DebuggerStepThrough]
            set
            {
                if (value != _inputTemperatureToolTip)
                {
                    _inputTemperatureToolTip = value;
                    OnPropertyChanged("InputTemperatureToolTip");
                }
            }
        }
        public string DirectionalToolTip
        {
            [DebuggerStepThrough] get { return _directionalToolTip; }
            [DebuggerStepThrough]
            set
            {
                if (value != _directionalToolTip)
                {
                    _directionalToolTip = value;
                    OnPropertyChanged("DirectionalToolTip");
                }
            }
        }
        public bool HasError
        {
            [DebuggerStepThrough] get { return _hasError; }
            [DebuggerStepThrough]
            set
            {
                if (value != _hasError)
                {
                    _hasError = value;
                    OnPropertyChanged("HasError");
                }
            }
        }
        public string MenuFile
        {
            [DebuggerStepThrough] get { return _menuFile; }
            [DebuggerStepThrough]
            set
            {
                if (value != _menuFile)
                {
                    _menuFile = value;
                    OnPropertyChanged("MenuFile");
                }
            }
        }
        public string MenuEdit
        {
            [DebuggerStepThrough] get { return _menuEdit; }
            [DebuggerStepThrough]
            set
            {
                if (value != _menuEdit)
                {
                    _menuEdit = value;
                    OnPropertyChanged("MenuEdit");
                }
            }
        }
        public string MenuView
        {
            [DebuggerStepThrough] get { return _menuView; }
            [DebuggerStepThrough]
            set
            {
                if (value != _menuView)
                {
                    _menuView = value;
                    OnPropertyChanged("MenuView");
                }
            }
        }
        public string MenuHelp
        {
            [DebuggerStepThrough] get { return _menuHelp; }
            [DebuggerStepThrough]
            set
            {
                if (value != _menuHelp)
                {
                    _menuHelp = value;
                    OnPropertyChanged("MenuHelp");
                }
            }
        }
        private bool _isFocused;
        public bool IsFocused
        {
            [DebuggerStepThrough]
            get { return _isFocused; }
            [DebuggerStepThrough]
            set
            {
                if (value != _isFocused)
                {
                    _isFocused = value;
                    OnPropertyChanged("IsFocused");
                }
            }
        }
        #endregion
        private void ExecuteSetCultureCommand(object obj)
        {
            if (SelectedCultureInfo == null) return;
            foreach (PropertyInfo pi in NotifyingStrings)
            {
                string displayValue = SelectedCultureInfo.GetString(pi.Name);
                if (displayValue != null)
                {
                    VmType.InvokeMember(pi.Name, BindingFlags.SetProperty, null, this, new object[] {displayValue});
                }
            }
            // try to convert the text box content to another cultural representation
            if (!Double.IsNaN(_inputParseResult))
            {
                InputTemperature = _inputParseResult.ToString("N", SelectedCultureInfo.FormatProvider);
            }
            _selfCaller = this["InputTemperature"];
            Thread.CurrentThread.CurrentCulture = SelectedCultureInfo.Culture;
            Thread.CurrentThread.CurrentUICulture = SelectedCultureInfo.Culture;
        }
        private void SetOutputTemperature(double tryResult, IFormatProvider ifp)
        {
            if (_temperatureConverter == null)
            {
                OutputTemperature = String.Empty;
                return;
            }
            double modelResult;
            if (IsDirectionalToggleChecked)
            {
                modelResult = _temperatureConverter.ToCelsius(tryResult);
            }
            else
            {
                modelResult = _temperatureConverter.ToFahrenheit(tryResult);
            }
            OutputTemperature = modelResult.ToString("N", ifp);
        }
        #region INotifyPropertyChanged Implementation
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = Interlocked.CompareExchange(ref PropertyChanged, null, null);
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }
        #endregion
        #region IDataErrorInfo Implementation
        public string Error
        {
            get { return String.Empty; }
        }
        public string this[string columnName]
        {
            get
            {
                IsFocused = false;
                StatusText = String.Empty;
                HasError = false;
                if (columnName == "InputTemperature")
                {
                    double tryResult;
                    if (Double.TryParse(InputTemperature,
                        NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands | NumberStyles.AllowLeadingSign,
                        SelectedCultureInfo.FormatProvider, out tryResult))
                    {
                        SetOutputTemperature(tryResult, SelectedCultureInfo.FormatProvider);
                        IsFocused = true;
                        _inputParseResult = tryResult;
                        return null;
                    }
                    HasError = true;
                    IsFocused = true;
                    return StatusText = SelectedCultureInfo.GetString(Invalidnumber);
                }
                return null;
            }
        }
        #endregion
    }
}